/*
 * 
 * Embedded Python 
 * 
 * 
 * Following the trail of EmbPerl here comes my initial version of EmbPy
 * 
 * For notes and instructions, please check http://code.google.com/p/embpy/
 * 
 * 
 * Hope I will have time to make this usable for others too.
 * 
 * Feel free to help and make suggestions!
 * 
 * 
 * 
 * Copyright 2010-01-27 
 * Ari Siitonen, ari.siitonen@gos.fi
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <dirent.h> 
#include <sys/stat.h> 


#include "config.h"
#include "Python.h"


// simple buffer size for all uses :) nothing shall be longer in this prototype
#define MAXLINELEN 10000
#define DEBUG 0
#define EXIT_FAILURE 1
#define myname "EmbPy"

char *infname=NULL,*oufname=NULL,*htmlfname=NULL,path[100],*cachepath;


void trim(char * line) {
   // trim leading spaces and tabs
   int len=strlen(line),p,t=0;
   char *apu,c0,c1,c2;
#if DEBUG
   printf("trim >'%s'\n",line);
#endif
   if (!len) return;
   apu=(char *)malloc(len+1);
   for (p=0;p<len+1;p++) {
      if (p<len)
	c0=line[p];
      else
	c0=0;
      
      if (p+1<len)
	c1=line[p+1];
      else
	c1=0;
      
      if (p+2<len)
	c2=line[p+2];
      else
	c2=0;
      if (t==0 && c0==' ')
	continue;
      apu[t]=c0;
      t++;
   }
   strcpy(line,apu);
# if DEBUG
   printf("trim <'%s'\n",line);
# endif
   free(apu);
}


int my_strncmp(char * s1,char * s2,int len) {
   // my version on strncmp is case isensitive
   int p,t=0,ret=0;
   char *a1,*a2,c0,c1,c2;
#if DEBUG
   printf("my_strncmp '%s','%s',%d\n",s1,s2,len);
#endif
   if (!len) return(0);
   a1=(char *)malloc(len+1);
   a2=(char *)malloc(len+1);
   for (p=0;p<len;p++)
     a1[p]=toupper(s1[p]),a2[p]=toupper(s2[p]);
   ret=strncmp(a1,a2,len);
   free(a1);
   free(a2);
#if DEBUG
   printf("my_strncmp '%s','%s',%d    => %d\n",s1,s2,len,ret);
#endif
   return(ret);
}

char *my_strstr(char * s1,char * s2) {
   // my version on strstr is case isensitive
   int p,t=0,len1,len2 ,pos;
   char *a1,*a2,c0,c1,c2,*ret=NULL;
#if DEBUG
   printf("my_strstr '%s','%s'\n",s1,s2);
#endif
   len1=strlen(s1);
   len2=strlen(s2);
   a1=(char *)malloc(len1+1);
   a2=(char *)malloc(len2+1);
   for (p=0;p<len1;p++)
     a1[p]=toupper(s1[p]);
   for (p=0;p<len2;p++)
     a2[p]=toupper(s2[p]);
   for (pos=0;pos<len1-len2;pos++) {
      if (strncmp(&a1[pos],a2,len2)==0) {
	 ret=&s1[pos];
      }
   }
   free(a1);
   free(a2);
#if DEBUG
   printf("my_strstr '%s','%s'    => '%s'\n",s1,s2,ret);
#endif
   return(ret);
}





FILE *inf,*ouf;
int len,p;
char *buf,apu[MAXLINELEN+1],mode=0;
int indent=0,quotemode=0;
char tbuf[MAXLINELEN+1],tquote=0;
int tmode=0;
int tp=0;


int run_python(char * code) {
  Py_Initialize();
  if (PyRun_SimpleString(code))
    return(-1);
  Py_Finalize();
  return(0);
}

int run_python_file(char * infname) {
  char *buf;
   printf("%s: About to run Python file '%s'\n",myname,infname);
#if 0
  inf=fopen(infname,"rb");
   if (!inf) {
     fprintf (stderr, "%s: Couldn't open file %s; %s\n", myname, infname, strerror (errno));
     return(EXIT_FAILURE);
   }
   fseek(inf,0,SEEK_END);
   len=ftell(inf);
   fseek(inf,0,SEEK_SET);
   buf=(char *)malloc(len+1);
   if (!fread(buf,len,1,inf)) {
     fprintf (stderr, "%s: Couldn't read file %s; %s\n", myname, infname, strerror (errno));
     return(EXIT_FAILURE);
   }
   fclose(inf);
   printf("%s: Red infile '%s' %d bytes\n",myname,infname,len);
   if (run_python(buf))
     return(EXIT_FAILURE);
   free(buf);
#else 
   {
     char cmd[100];
     int err;
     sprintf(cmd,"/usr/bin/python %s 2>/tmp/embpy.%d.err",infname,getpid());
     //     sprintf(cmd,"/usr/bin/python %s ",infname);
     printf(">>>>>>>%s\n",cmd);
     err=system(cmd);
     printf(">>>>>>>ret %d\n",err); 
     if (err) {
       fprintf (stderr, "%s: Couldn't run %s #%d \n", myname, infname,err);
       return(10);
     } else {
       sprintf(cmd,"/tmp/embpy.%d.err",getpid());       
       //       if (unlink(cmd)) {
       // }
     }
   }
#endif
   return(0);
}

void emitline(int i,char *buf,int from, int till) {
   /*
    * 
    * Internal function to emit pieces of buffer as Python code to outfile
    * 
    * Does late analysis for <INPUT > tags to automatically fill them with form data
    * 
    * todo:
    * - make parsing generic, will need to pick up more tags: select et.al.
    * 
    */

   char line[MAXLINELEN+1];
   int z,len,p;

   len=till-from;
   if (len<1) return;

   if (len>MAXLINELEN) 
     {
	fprintf(stderr,"Internal buffer overflow\n");
	exit(1);
     }
   strncpy(line,&buf[from],till-from);
   line[till-from]=0;
   for (p=0;p<len;p++) { 
     if (line[p]=='#')       // find comment lines
       strcpy(line,"pass");  // put sentinel, not to confuse python indentation
     else if (line[p]!=' ' && line[p]!='\t')
       break;
   }
   len=strlen(line);


#if 0
#if DEBUG
   printf("####################### emitline %d <%s> len=%d\n",i,line,len);
#endif
   for (p=0;p<len;p++) {
     char c0,c1,c2;
     if (p<len)
       c0=line[p];
     else
       c0=0;
     
     if (p+1<len)
       c1=line[p+1];
     else
       c1=0;
     
     if (p+2<len)
       c2=line[p+2];
     else
       c2=0;
     
     if (c0=='<')  {  // tag start
       tmode=1;
       tquote=0;
       tp=0;
     } else if (c0=='>' && tmode && tp)  {  // tag ends
       tmode=0;
#if DEBUG
       printf("tag found: %s\n",tbuf);
#endif
       if (my_strncmp(tbuf,"INPUT",5)==0) {
	 char s[MAXLINELEN+1],ss[MAXLINELEN+1],sss[MAXLINELEN+1],*pp,*px,inp[MAXLINELEN+1];
#if DEBUG
	 printf("tag *** INPUT found: %s\n",tbuf);
#endif
	 if ((pp=my_strstr(tbuf," NAME="))) {  // do some fancy parsing --- rewrite asap!
	   char hunt=' ';
	   int x=0;
	   pp+=6;
	   if (*pp == '"' || *pp =='\'')
	     hunt=*pp,pp++;
	   inp[0]=0;
	   
	   for (x=0;x<strlen(pp);x++)
	     if (pp[x]==hunt) 
	       {
		 strncpy(inp,pp,x);
		 inp[x]=0;
		 break;
	       }
#if DEBUG
	   printf("tag *** INPUT found: %s ****** with name %s\n",tbuf,inp);
#endif
	   
	   if (inp[0]) { // if we actually got INPUT's name
	     if (!my_strstr(tbuf," VALUE=")) {
	       sprintf(s," VALUE=\"'''+fdat.get('%s','')+'''\" ",inp); //fix value 
	       strncpy(ss,line,p);
	       ss[p]=0;
	       strcpy(sss,&line[p]);
	       sprintf(line,"%s%s%s",ss,s,sss);
	     }
	   }
	   
	 }
       }
     } else if (tmode)  {
       char ch,prev;    // pick up tag's internals omitting extra spaces and lf's
       if (tp)
	 prev=tbuf[tp-1];
       else 
	 prev=0;
       ch=c0;
       if (ch =='\n')
	 ch=' ';
       if (!tquote) {
	 if (ch==' ' && prev==' ')
	   continue;
	 if (ch==' ' && c1==' ')
	   continue;
	 if (ch==' ' && prev=='=')
	   continue;	    
	 if (ch==' ' && c1=='=')
	   continue;
	 if (ch==' ' && !tp)
	   continue;
       }
       if (ch=='"' || ch=='\'') {  // figure out quotes properly .. no space supression on quoted text
	 if (!tquote)
	   tquote=ch;
	 else if (tquote == ch)
	   tquote=0;
       }
       tbuf[tp]=ch;
       if (tp<MAXLINELEN)
	 tp++;
       tbuf[tp]=0;	    
     }
   }
#endif   



   // now actually emit the code to Python file, 
   if (!quotemode) 
     for (z=0;z<i+indent;z++)   // if triple quoting, no indentation
       fprintf(ouf,"\t");
   fprintf(ouf,"%s",line);      
   //   fprintf(ouf,"%s\n",line);      
     // replace linefeeds with \n
     /*     int p=0;
     for (p;p<strlen(line);p++) {
       if (line[p]=='\n')
	 fprintf(ouf,"\\n");      
       else 
	 fprintf(ouf,"%c",line[p]);      
	 }
	 }*/
   for (p=0;p<len+2;p++) {  // figure quote mode for next line.
     char c0,c1,c2;
     if (p<len)
       c0=line[p];
     else
       c0='0';
     if (p+1<len)
       c1=line[p+1];
     else
       c1=0;
     if (p+2<len)
       c2=line[p+2];
     else
       c2=0;
     
     if (c0 == '\'' && c1 == '\'' && c2 == '\'') {  // detect triple quote
       quotemode=!quotemode;
     }
   }
   if (quotemode)
     fprintf(ouf,"\\n");      
   else 
     fprintf(ouf,"\n");      
}


void emitcode(int i,char *buf) {
   /*
    * Internal func to split block of Python code into lines of code 
    * 
    * i: python's indentation level
    */
   
   int p=0,s=0;
   p=0;
   while (buf[p]) {
      if (buf[p]=='\n') { 
	 emitline(i,buf,s,p);
	 s=p+1;
      }
      p++;
   }
   emitline(i,buf,s,p);
	
}



// struct to hold recognized [$ xxx $] commands
// ichange : indentation change for next lines
// ioffset : indentation offset from current, just for this command
// output  : shoud we output the code ?

struct comstype {
  char *cmd;
  int ichange;
  int ioffset;
  int output;
  int addcolon;
} coms[]={
  {"for",  1, 0,1,1},
  {"break",0, 0,1,0},
  {"while",1, 0,1,1},
  {"if",   1, 0,1,1},
  {"else", 0,-1,1,1},
  {"elif", 0,-1,1,1},
  {"def",   1,0,1,1},
  {"end", -1, 0,0,1},
  {NULL,   0, 0,0,0}
};

void dumper(char *d,char * str, int len) 
{
   int p;
   printf("\n            dumper '%s': ",d);
   for (p=0;p<len;p++)
     if (str[p]=='\n')
       printf("\\n");
   else if (str[p]=='\t')
       printf("\\t");
   else
     printf("%c",str[p]);
   printf("\n\n");
}



void output(char mode,char * strin,int from, int till) {
   /*
    * This is the fun part.
    * 
    * this function gets blocks of Embedded Python.
    * the mode tells what kind of block we have at our hands.
    * 0 -> plain text -> make python just print it
    * # -> comment
    * - -> execute
    * + -> evaluate and feed output to html
    * $ -> command: for , while, if , end
    * 
    * for debug purposes we do not crash on errors within python, try & except will just produce comment
    * 
    */
   
  char s[MAXLINELEN]; // we need some playground here -- suppose +10k char long lines do not exist ;)
  char *str;   
  int len=till-from;
  
  if (len<1) return;
  //first make local copy of the snippet -> str
  str=(char *)malloc(len+1);
  strncpy(str,&strin[from],len);
  str[len]=0;
  if (mode == 0)  {
    sprintf(s,"embpy_out('''%s''')\n",str);
    emitcode(0,s);
  } else if (mode == '$') {
      int cp=0,cok=0;
      // for eval blocks strip unnecessary white space
      trim(str);
#if DEBUG      
      printf("command ]%s[\n",str);
#endif
      for (cp=0;coms[cp].cmd;cp++)
	if (my_strncmp(str,coms[cp].cmd,strlen(coms[cp].cmd))==0) {
	   if (coms[cp].output) {
	      strncpy(str,coms[cp].cmd,strlen(coms[cp].cmd)); // lowercase
	      if (coms[cp].addcolon) 
		sprintf(s,"%s:",str);
	      else
		strcpy(s,str);
	      emitcode(coms[cp].ioffset,s);
	   }
	   indent+=coms[cp].ichange;
	   cok=1;
	   break;
	}
      if (!cok) {
	if (my_strncmp(str,"hidden",strlen("hidden"))==0) {
	  emitcode(0,"embpy_out('<HIDDEN>')\n");
	} else {
	  // unrecognized command -> emit error
	  sprintf(s,"embpy_out('''<!-- EmbPy unrecognized command : '[$%s$]' -->''')\n\n",str);
	  emitcode(0,s);
	}
      }
#if DEBUG
      printf("command %s done\n",str);      
#endif
   } else if (mode == '+') {
      sprintf(s,"try:");
      emitcode(0,s);
      
      sprintf(s,"__temp__=%s\nembpy_out(str(__temp__))\n",str);      
      emitcode(1,s);
      sprintf(s,"except:");
      emitcode(0,s);
      sprintf(s,"embpy_out('''<!-- EmbPy fails to Evaluate : '' -->''')\n\n",str);
      emitcode(1,s);
	   
   } else if (mode == '-') {
      sprintf(s,"try:");
      emitcode(0,s);
      
      emitcode(1,str);
      sprintf(s,"except:");
      emitcode(0,s);
      sprintf(s,"embpy_out('''<!-- EmbPy fails to Execute : '' -->''')\n\n",str);
      emitcode(1,s);
   } else if (mode == '#') {
      sprintf(s,"embpy_out('''<!-- EmbPy Comment : '%s' -->''')\n\n",str);
      emitcode(0,s);
   }
   free(str);
}

int force=1;

int unlink_if_exists(char *fn) {
    struct stat s;  
    if (!stat(fn,&s)) {
      //it exists, so unlink!
      return(unlink(fn));
    } else 
      return(0);
}

int embpy (char * infname,char * oufname,char *htmlfname,char *args,char *cookies,char *ip,char *host,char *referer,char * useragent) {
  int bstart=0,estart=0;

  printf("Embpy preprocessor: %s -> %s\n\n",infname,oufname);
  // now check if cached Python file is fresh
  if (force) { 
      printf("%s: Regeneration forced, ignoring cache \n",myname);
      //remove old .py and .html files
      if (unlink_if_exists(oufname)) 
	return(99);
      if (unlink_if_exists(htmlfname)) 
	return(98);
  } else {
  //remove .html files
    int tainted=0;
    struct stat oufs,infs;
    if (unlink_if_exists(htmlfname)) 
      return(98);

    if (stat(oufname,&oufs)) {
      tainted=1;

    } else {

      if (stat(infname,&infs)) {
	fprintf (stderr, "%s: Couldn't open file %s; %s\n", myname, infname, strerror (errno));
	return(1);
      }
      if (oufs.st_mtime<infs.st_mtime)
	tainted=1;
    }
    if (!tainted) {
      printf("%s: Cache file up to date, using cached Python file \n",myname);
      if (unlink_if_exists(oufname)) 
	return(97);
      run_python_file(oufname);
    } else
      printf("%s: Cache file outdated (%ld:%ld), need to regerate \n",myname,(infs.st_mtime-oufs.st_mtime)/60,(infs.st_mtime-oufs.st_mtime)%60,myname);
  }

  inf=fopen(infname,"rb");
   if (!inf) {
     fprintf (stderr, "%s: Couldn't open file %s; %s\n", myname, infname, strerror (errno));
     return(2);
   }
   fseek(inf,0,SEEK_END);
   len=ftell(inf);
   fseek(inf,0,SEEK_SET);
   buf=(char *)malloc(len+2);
   if (!fread(buf,len,1,inf)) {
     fprintf (stderr, "%s: Couldn't read file %s; %s\n", myname, infname, strerror (errno));
     return(3);
   }
   //   buf[len-1]='X';
   //buf[len]='Y';
   //buf[len+1]='Z';
   fclose(inf);
   printf("%s: Red infile '%s' %d bytes\n",myname,infname,len);
   ouf=fopen(oufname,"w");
   if (!ouf) {
     fprintf (stderr, "%s: Couldn't open file %s; %s\n", myname, oufname, strerror (errno));
     return(4);
   }
   
   fprintf(ouf,"# -*- coding: utf-8 -*-\n#EmbPy generated from %s\nimport sys\nfrom embpy import *\nembpy_open('%s')\nparse_args('''%s''')\n",infname,htmlfname,args);
   if (cookies)
     fprintf(ouf,"parse_cookies('''%s''')\n",cookies);
   else
     fprintf(ouf,"# cookies: %s\n",cookies);
   fprintf(ouf,"edat['REMOTEIP']='%s'\n",ip);
   fprintf(ouf,"edat['REFERER']='%s'\n",referer);
   fprintf(ouf,"edat['USERAGENT']='%s'\n",useragent);
   //   emitcode(0,"parse_args(args)"); // our support library -- initializations et al
   for (p=0;p<len+1;p++) {
      char c0,c1,c2;
      if (p<len)
	c0=buf[p];
      else {
	 c0=0; // eof
	 if (!mode)  {
	    output(mode,buf,bstart,len-1);
	    break;
	 }
	 
      }
      
      if (p+1<len)
	c1=buf[p+1];
      else
	c1=0;
      if (p+2<len)
	c2=buf[p+2];
      else
	c2=0;
      
      if (c0 == '[' && (c1 == '+' || c1 == '-' ||c1 == '$' ||c1 == '#') ) { // start of embedding -> output text block
	 if (c1 == '+' || c1 == '-' ||c1 == '$' ||c1 == '#')  {  
	    output(mode,buf,bstart,p);	 
	    mode=c1;
	    estart=p+2; 
	 }
      } else if (c1 == ']' && (c0 == '+' || c0 == '-' ||c0 == '$' ||c0 == '#') && mode) { // end of embedding
	 output(mode,buf,estart,p);
	 bstart=p+2;
	 mode=0;
      }
      
      
   }
   emitcode(0,"embpy_close()\n");
   fclose(ouf);
   if (run_python_file(oufname))
     return(5);
   return(0);
}



#ifndef BUILDMODULE
main(int argc, char **argv)
{
   /*
    *
    * Main program.
    *
    * needs more options and checking, but works already
    * 
    * 
    * 
    */
   
  int r,bp=0,index,fail;
  char *buf,c;
  char basename[100];

  int opterr = 0;
  static int verbose_flag;
  
  while (1) {
    static struct option long_options[] =
      {
	/* These options set a flag. */
	{"verbose", no_argument,       &verbose_flag, 1},
	{"quiet",   no_argument,       &verbose_flag, 0},
	{"force",   no_argument,       &force, 1},
	/* These options don't set a flag.
	   We distinguish them by their indices. */
	{"input",  required_argument, 0, 'i'},
	{"output",  required_argument, 0, 'o'},
	{"html",  required_argument, 0, 'x'},
	{"help",    no_argument, 0, 'h'},
	{"version",    no_argument, 0, 'v'},
	{0, 0, 0, 0}
      };
    /* getopt_long stores the option index here. */
    int option_index = 0;
    
    c = getopt_long (argc, argv, "abc:d:f:",
		     long_options, &option_index);
    
    /* Detect the end of the options. */
    if (c == -1)
      break;
    
    switch (c)
      {
      case 0:
	/* If this option set a flag, do nothing else now. */
	if (long_options[option_index].flag != 0)
	  break;
	printf ("option %s", long_options[option_index].name);
	if (optarg)
	  printf (" with arg %s", optarg);
	printf ("\n");
	break;
	
      case 'i':
	infname=(char *)malloc(strlen(optarg)+1);
	strcpy(infname,optarg);
	break;

      case 'v':
	printf ("\nEmbPy version %s\n\
Copyright (C) 2011 Ari Siitonen\n\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,\n\
to the extent permitted by law.\n\n\
Project web site: <http://code.google.com/p/embpy/>.\n\n",VERSION);
	return(0);
	break;


      case 'h':
	printf ("Usage: embpy --input input-file --output output-file\n\n\
  --verbose\n\
                 print all informative messages to stdout\n\
  --quiet\n\
                 suppress all informative printing\n\
  --help\n\
                 display this help and exit\n\
  --version\n\
                 output version number and exit\n\n\
Input and output files must be defined.\n\n");
	return(0);
	break;
	
      case 'o':
	oufname=(char *)malloc(strlen(optarg)+1);
	strcpy(oufname,optarg);
	break;
      case 'x':
	htmlfname=(char *)malloc(strlen(optarg)+1);
	strcpy(htmlfname,optarg);
	break;
	
      case '?':
	/* getopt_long already printed an error message. */
	break;
	
      default:
	fprintf (stderr, "%s: Unrecognized option '%s'\n", myname,long_options[option_index].name);
	exit (EXIT_FAILURE);
      }
  }
  
  if (verbose_flag)
    puts ("verbose flag is set");
  
  
  if (!infname) {
    fprintf (stderr, "%s: No input file defined; cannot proceed\n", myname);
    exit (EXIT_FAILURE);
  }

  {  // path checking...
    int p,slash=-1;
    DIR *dp;
    // if no output file specified -> 
    // 1) check if earlier cache file exists and is valid
    // 2) if not -> parse now´and store to cache file

    
    for (p=strlen(infname)-1;p;p--) {
      if (infname[p]=='/') {
	slash=p;
	break;
      }
    }
    if (slash==-1) { // no slash, use pwd
      strcpy(path,"./");
      strcpy(basename,infname);
    } else {
      slash++;
      strncpy(path,infname,slash);
      path[slash]=0;
      strncpy(basename,&infname[slash],strlen(infname)-slash);
      basename[strlen(infname)-slash]=0;
    }
    cachepath=(char *)malloc(strlen(basename)+20);
    sprintf(cachepath,"%scache",path);


    dp = opendir(cachepath);
    if(dp == NULL) {
      if (mkdir(cachepath,S_IRWXU|S_IRWXG)) {
	fprintf (stderr, "%s: Couldn't create cache directory %s; %s\n", myname, cachepath, strerror (errno));
	exit (EXIT_FAILURE);
      }
      printf("%s: Created cache directory for precompiled Python files: %s\n",myname,cachepath);
    } 

  }
  if (!oufname) {
    oufname=(char *)malloc(strlen(basename)+strlen(path)+20);
    sprintf(oufname,"%scache/%s.py",path,basename);

    printf("%s: No outfile specified, Python Output goes to : %s\n",myname,oufname);
  }

  if (!htmlfname) {
    htmlfname=(char *)malloc(strlen(basename)+strlen(cachepath)+20);
    sprintf(htmlfname,"%s/%s.html",cachepath,basename);
    printf("%s: No htmlfile specified, Html Output goes to : %s\n",myname,htmlfname);    
  }
  fail=embpy(infname,oufname,htmlfname,"a=b?c=d","","","","","");
  if (fail) {
    char cmd[100];
     sprintf(cmd,"cat /tmp/embpy.%d.err",getpid());
     system(cmd);
  }
  return(fail);
}
#endif
